﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FastTests;
using FastTests.Utils;
using Raven.Client.Documents.Indexes;
using Raven.Client.Documents.Operations.Backups;
using Raven.Client.Documents.Operations.Revisions;
using Raven.Client.Documents.Smuggler;
using Raven.Client.ServerWide.Operations;
using Raven.Server.Documents;
using Raven.Server.ServerWide.Context;
using Raven.Tests.Core.Utils.Entities;
using Tests.Infrastructure;
using Xunit;
using Xunit.Abstractions;

namespace SlowTests.Issues
{
    public class RavenDB_16378 : RavenTestBase
    {
        public RavenDB_16378(ITestOutputHelper output) : base(output)
        {
        }

        [RavenFact(RavenTestCategory.Smuggler)]
        public async Task TombstonesOfArtificialDocumentsShouldNotBeRestored()
        {
            using (var store = GetDocumentStore())
            {
                await new Users_ByName_Count().ExecuteAsync(store);

                using (var session = store.OpenSession())
                {
                    session.Store(new User() { Name = "Arek" }, "users/arek");

                    session.SaveChanges();
                }

                await Indexes.WaitForIndexingAsync(store);


                using (var session = store.OpenSession())
                {
                    session.Delete("users/arek"); // delete source document so we'll have tombstones of artificial documents generated by the index

                    session.SaveChanges();
                }

                await Indexes.WaitForIndexingAsync(store);

                var backupPath = NewDataPath(suffix: "BackupFolder");
                var config = Backup.CreateBackupConfiguration(backupPath);
                var backupTaskId = await Backup.UpdateConfigAndRunBackupAsync(Server, config, store);

                // restore

                var backupDirectory = Directory.GetDirectories(backupPath).First();

                var databaseName = GetDatabaseName() + "restore";

                var files = Directory.GetFiles(backupDirectory)
                    .Where(BackupUtils.IsBackupFile)
                    .OrderBackups()
                    .ToArray();

                RestoreBackupConfiguration config2 = new RestoreBackupConfiguration()
                {
                    BackupLocation = backupDirectory,
                    DatabaseName = databaseName,
                    LastFileNameToRestore = files.Last()
                };

                RestoreBackupOperation restoreOperation = new RestoreBackupOperation(config2);
                await (await store.Maintenance.Server.SendAsync(restoreOperation))
                    .WaitForCompletionAsync(TimeSpan.FromSeconds(30));

                using (var storeOfRestoredDb = GetDocumentStore(new Options() { CreateDatabase = false, ModifyDatabaseName = s => databaseName }))
                {

                    var db = await GetDatabase(storeOfRestoredDb.Database);

                    using (db.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext ctx))
                    using (ctx.OpenReadTransaction())
                    {
                        var tombstones = db.DocumentsStorage.GetTombstonesFrom(ctx, 0, 0, int.MaxValue).ToList();

                        Assert.Equal(1, tombstones.Count);
                        Assert.Equal("users/arek", tombstones[0].LowerId);
                    }
                }
            }
        }

        [RavenFact(RavenTestCategory.Smuggler)]
        public async Task ShouldPreserveTombstoneFlagsAfterRestore()
        {
            using (var store = GetDocumentStore())
            {
                var configuration = new RevisionsConfiguration
                {
                    Default = new RevisionsCollectionConfiguration
                    {
                        Disabled = false,
                        MinimumRevisionsToKeep = 10
                    },

                    Collections = new Dictionary<string, RevisionsCollectionConfiguration>
                    {
                        ["Users"] = new RevisionsCollectionConfiguration
                        {
                            Disabled = false,
                            MinimumRevisionsToKeep = 5
                        }
                    }
                };

                await RevisionsHelper.SetupRevisionsAsync(store, configuration: configuration);

                using (var session = store.OpenSession())
                {
                    session.Store(new User() { Name = "Arek" }, "users/arek");

                    session.SaveChanges();
                }

                using (var session = store.OpenSession())
                {
                    session.Delete("users/arek"); // this will create tombstone with HasRevisions flags

                    session.SaveChanges();
                }

                var backupPath = NewDataPath(suffix: "BackupFolder");

                var config = Backup.CreateBackupConfiguration(backupPath);
                var backupTaskId = await Backup.UpdateConfigAndRunBackupAsync(Server, config, store);

                // restore

                var backupDirectory = Directory.GetDirectories(backupPath).First();

                var databaseName = GetDatabaseName() + "restore";

                var files = Directory.GetFiles(backupDirectory)
                    .Where(BackupUtils.IsBackupFile)
                    .OrderBackups()
                    .ToArray();

                RestoreBackupConfiguration config2 = new RestoreBackupConfiguration()
                {
                    BackupLocation = backupDirectory,
                    DatabaseName = databaseName,
                    LastFileNameToRestore = files.Last()
                };

                RestoreBackupOperation restoreOperation = new RestoreBackupOperation(config2);
                await (await store.Maintenance.Server.SendAsync(restoreOperation))
                    .WaitForCompletionAsync(TimeSpan.FromSeconds(30));

                using (var storeOfRestoredDb = GetDocumentStore(new Options() { CreateDatabase = false, ModifyDatabaseName = s => databaseName }))
                {

                    var db = await GetDatabase(storeOfRestoredDb.Database);

                    using (db.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext ctx))
                    using (ctx.OpenReadTransaction())
                    {
                        var tombstones = db.DocumentsStorage.GetTombstonesFrom(ctx, 0, 0, int.MaxValue).ToList();

                        Assert.Equal(1, tombstones.Count);
                        Assert.Equal(DocumentFlags.HasRevisions, tombstones[0].Flags);
                    }
                }
            }
        }

        private class Users_ByName_Count : AbstractIndexCreationTask<User, Users_ByName_Count.Result>
        {
            public class Result
            {
                public string Name { get; set; }

                public int Count { get; set; }
            }

            public Users_ByName_Count()
            {
                Map = users => from u in users select new { u.Name, Count = 1 };
                Reduce = results => from result in results group result by result.Name into g select new { Name = g.Key, Count = g.Sum(x => x.Count) };
                OutputReduceToCollection = "UserNames";
                PatternForOutputReduceToCollectionReferences = x => $"UserNames/{x.Name}";
            }
        }
    }
}
